﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Scheduling.Models;
using Scheduling.Models.Helpers;
using Scheduling.Models.Interfaces;

namespace Scheduling.Models.Helpers
{
    public class Timeslot
    {
        public DateTime Start;
        public readonly TimeSpan Length = new TimeSpan(2, 0, 0);
        public DateTime End { get { return Start + Length; } }
        public Room Room;
        public override string ToString()
        {
            return string.Format("{0} @ {1} to {2}", Room.ID, Start, End);
        }
    }
    public class MultipleBooker
    {
        public List<Booking> BookingList;
        public List<Room> RoomList;

        public List<Booking> NewBookings;
        public Dictionary<BookingRequest, List<Timeslot>> RequestTimeSlots;
        public MultipleBooker()
        {
            var bookingLoader = IoC.Resolve<IBookingLoader>();
            var requestLoader = IoC.Resolve<IBookingRequestLoader>();
            var roomLoader = IoC.Resolve<IRoomLoader>();

            BookingList = bookingLoader.GetBookings();
            RoomList = roomLoader.GetRooms();
            NewBookings = new List<Booking>();
            RequestTimeSlots = new Dictionary<BookingRequest, List<Timeslot>>();
        }
        public string GenerateTransaction()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("BEGIN TRANSACTION");
            foreach (var b in NewBookings)
            {
                sb.AppendFormat("INSERT INTO Bookings(CourseInstanceID, UserID, RoomID, StartDate, EndDate) VALUES({0}, '{1}', '{2}', '{3}', '{4}');",
                    b.CourseInstance.ID, b.User.ID, b.Room.ID, b.StartDate, b.EndDate);
                sb.AppendLine();
            }
            sb.AppendLine("COMMIT");
            return sb.ToString();
        }
        public bool GenerateBookings(List<BookingRequest> Requests, int TargetYear, int TargetPeriod)
        {
            var remainingRequests = Requests.ToList();
            RequestTimeSlots.Clear();
            NewBookings.Clear();

            var timeSlots = GetTimeSlots(TargetPeriod, TargetYear);
            bool scheduleFull = false;

            foreach (var request in Requests)
            {
                RequestTimeSlots.Add(request, new List<Timeslot>());
            }

            while (remainingRequests.Count > 0)
            {
                foreach (var request in remainingRequests)
                {
                    bool success = ProcessRequest(request, timeSlots);
                    if (success)
                        request.LectureCount--;
                    else
                    {
                        remainingRequests.Remove(request);
                        scheduleFull = true;
                        break;
                    }
                }
                remainingRequests = remainingRequests.Where(r => r.LectureCount > 0).ToList();
            }

            if (scheduleFull)
                return false;
            return true;
        }

        bool ProcessRequest(BookingRequest request, List<Timeslot> timeSlots)
        {
            List<Timeslot> preferedTimeSlots = new List<Timeslot>();
            Timeslot selectedSlot = null;
            preferedTimeSlots = timeSlots.Where(t => t.Start.Hour == request.Time && t.Room.ID == request.RoomPattern).ToList();
            selectedSlot = TryBooking(request, preferedTimeSlots);
            if (selectedSlot != null) { timeSlots.Remove(selectedSlot); return true; }

            if (request.Priority == Priority.Time)
                preferedTimeSlots = timeSlots.Where(t => t.Start.Hour == request.Time).ToList();
            else
                preferedTimeSlots = timeSlots.Where(t => t.Room.ID == request.RoomPattern).ToList();
            selectedSlot = TryBooking(request, preferedTimeSlots);
            if (selectedSlot != null) { timeSlots.Remove(selectedSlot); return true; }

            preferedTimeSlots = timeSlots.ToList();
            selectedSlot = TryBooking(request, preferedTimeSlots);
            if (selectedSlot != null) { timeSlots.Remove(selectedSlot); return true; }

            return false;

        }

        Timeslot TryBooking(BookingRequest request, List<Timeslot> timeslots)
        {
            timeslots = (from t in timeslots
                         where (t.Room.Capacity >= request.RequiredCapacity)
                         orderby t.Room.Type != request.RoomType
                         /*orderby r.Subject != request.Subject*/
                         orderby StringSimilarityScore(t.Room.ID, request.RoomPattern) descending
                         select t).ToList();

            while (timeslots.Count > 0)
            {
                int MaxDist = -1;
                Timeslot selectedTimeSlot = null;

                //TODO: Optimize this algorithm
                //Find booking furthest from last bookings

                /*
                //Faster but less accurate version avg: o(nlogn) max: o(n2)
                foreach (var tx in RequestTimeSlots[request])
                {
                    var ts = timeslots.OrderBy(t => Math.Abs((int)(t.Start - tx.Start).TotalDays)).First();
                    int diff = Math.Abs((int)(ts.Start - tx.Start).TotalDays);
                    if (diff > MaxDist)
                    {
                        MaxDist = diff;
                        selectedTimeSlot = ts;
                    }
                }
                */

                //Slower but more accurate version avg: o(n2)
                foreach (Timeslot t in timeslots)
                {
                    foreach (Timeslot tx in RequestTimeSlots[request])
                    {
                        int diff = Math.Abs((int)(t.Start - tx.Start).TotalDays);
                        if (diff > MaxDist)
                        {
                            MaxDist = diff;
                            selectedTimeSlot = t;
                        }
                    }
                }


                //No timeslot found, pick middle
                if (selectedTimeSlot == null)
                    selectedTimeSlot = timeslots[timeslots.Count / 2];

                //Check for valid day
                if (!(request.AllowedDays.ContainsKey(selectedTimeSlot.Start.DayOfWeek) &&
                    (request.AllowedDays[selectedTimeSlot.Start.DayOfWeek] == DayType.Every ||
                     request.AllowedDays[selectedTimeSlot.Start.DayOfWeek] == DayType.Odd && SchedulingHelper.GetWeekFromDate(selectedTimeSlot.Start) % 2 != 0 ||
                     request.AllowedDays[selectedTimeSlot.Start.DayOfWeek] == DayType.Even && SchedulingHelper.GetWeekFromDate(selectedTimeSlot.Start) % 2 == 0
                    )))
                {
                    timeslots.Remove(selectedTimeSlot);
                    continue;
                }

                Booking booking = new Booking()
                {
                    ID = 0,
                    StartDate = selectedTimeSlot.Start,
                    EndDate = selectedTimeSlot.End,
                    Room = selectedTimeSlot.Room,
                    CourseInstance = request.CourseInstance,
                    User = request.Booker
                };

                NewBookings.Add(booking);
                BookingList.Add(booking);
                RequestTimeSlots[request].Add(selectedTimeSlot);
                return selectedTimeSlot;
            }
            return null;
        }

        Room GetBestRoom(BookingRequest request, List<Room> RemainingRooms)
        {
            var roomPriority = (from r in RoomList
                                where (r.Capacity >= request.RequiredCapacity)
                                orderby r.Type != request.RoomType
                                /*orderby r.Subject != request.Subject*/
                                orderby StringSimilarityScore(r.Name, request.RoomPattern) descending
                                select r).ToList();
            foreach (var room in RemainingRooms)
            {
                roomPriority.Remove(room);
            }
            return roomPriority.First();
        }

        //String Distance modified Levenshtein search
        double StringSimilarityScore(string s, string t)
        {
            int diff = Math.Max(s.Length, t.Length);
            int pos = 0;
            while (pos < s.Length && pos < t.Length)
            {
                if (s[pos] == t[pos]) diff--;
                else break;
                pos++;
            }
            return diff;
        }

        List<Timeslot> GetTimeSlots(int Period, int Year)
        {
            DateTime[] periods = SchedulingHelper.GetPeriodStartEndDate(Year, Period);
            DateTime Start = periods[0];
            DateTime End = periods[1];

            List<Timeslot> TimeSlots = new List<Timeslot>();

            while (Start < End)
            {
                for (int h = 8; h < 16; h += 2)
                {
                    if (h == 12) h++;
                    foreach (Room room in RoomList)
                    {
                        Timeslot ts = new Timeslot() { Start = Start.AddHours(h), Room = room };
                        Booking temp = new Booking() { ID = 0, StartDate = ts.Start, Room = room, EndDate = ts.End };
                        bool invalidTimeSlot = false;
                        foreach (Booking booking in BookingList)
                        {
                            if (temp.CollidesWith(booking))
                                invalidTimeSlot = true;
                        }
                        if (!invalidTimeSlot)
                            TimeSlots.Add(ts);
                    }
                }
                Start = Start.AddDays(1);
            }

            return TimeSlots;
        }
    }
}
